/**
 *  Copyright (C) 2008-2013  Telosys project org. ( http://www.telosys.org/ )
 *
 *  Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *          http://www.gnu.org/licenses/lgpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.telosys.common.data;

import java.util.Collections;
import java.util.LinkedList;

import org.telosys.common.TelosysRuntimeException;

/**
 * Ordered and sortable list of beans  <br>
 * 
 * This class is an extension of LinkedList <br>
 * it implements Collection, List, Serializable
 * 
 * @author Laurent GUERIN
 *  
 */
public class BeanList<T> extends LinkedList<T>  // then implements 
{
	private static final long serialVersionUID = 1L;

	private final static int UNSELECTED = -1 ;
    
    //-----------------------------------------------------------------------------
    // ATTRIBUTES(S)
    //-----------------------------------------------------------------------------

    //private LinkedList<T> _list           = null;

    private Class<T>      _elementClass   = null;

    private int           _iCurrentIndex  = 0; // Used by getFirstElement & getNextElement

    private boolean       _bSorted        = false;

    private String        _sSortedByField = "";

    private boolean       _bSortOrder     = FieldComparer.ASC;

    private int           _iSelectedIndex = UNSELECTED;  
    
    //-----------------------------------------------------------------------------
    // CONSTRUCTOR(S)
    //-----------------------------------------------------------------------------
    public BeanList(Class<T> eltClass)
    {
        if (eltClass == null)
        {
            throw new IllegalArgumentException("Class argument is null ");
        }
        _elementClass = eltClass;
        _iCurrentIndex = 0;
    }

    //-----------------------------------------------------------------------------
    // PRIVATE METHOD(S)
    //-----------------------------------------------------------------------------
    /**
     * Returns the boolean order flag according to the given order in string format
     * @param sOrder
     * @return
     */
    private boolean getSortOrderFlag(String sOrder)
    {
        if (sOrder == null)
        {
            throw new IllegalArgumentException("order is 'null' ");
        }
        String sOrderUC = sOrder.trim().toUpperCase();
        if (sOrderUC.startsWith("ASC"))
        {
            return FieldComparer.ASC;
        }
        else if (sOrderUC.startsWith("DESC"))
        {
            return FieldComparer.DESC;
        }
        else
        {
            throw new IllegalArgumentException("invalid order '" + sOrder + "' ( 'ASC' or 'DESC' expected )");
        }
    }

    /**
     * Sorts the list according to the given parameters
     * @param sFieldName : field to use as sort criteria
     * @param bOrder : sort order flag ( ASC / DESC )
     * @param bFlagIgnoreCase : ignore case flag 
     */
    private void sortList(String sFieldName, boolean bOrder, boolean bFlagIgnoreCase)
    {
        if (sFieldName == null)
        {
            throw new IllegalArgumentException("field name is 'null' ");
        }
        String sField = sFieldName.trim();
        Collections.sort(this, new FieldComparer(sField, bOrder, bFlagIgnoreCase));
        _bSorted = true;
        _sSortedByField = sField;
        _bSortOrder = bOrder;
    }

    
    //-----------------------------------------------------------------------------
//    /**
//     * Appends the given element to the end of the list
//     * 
//     * @param newElement
//     */
//    public void addElement(T newElement)
//    {
//        this.addLast(newElement);
//    }

    //-----------------------------------------------------------------------------
    // Inserts the specified element at the specified position in the list.
    //-----------------------------------------------------------------------------
    public void insert(int index, T newElement)
    {
    	super.add(index, newElement);
    }

    //-----------------------------------------------------------------------------
    // Replaces the element at the specified position in the list with the
    // specified element.
    //-----------------------------------------------------------------------------
    public T replace(int index, T newElement)
    {
        return super.set(index, newElement);
    }

//    //-----------------------------------------------------------------------------
//    // Removes the first occurrence of the specified element in the list.
//    //-----------------------------------------------------------------------------
//    public boolean removeElement(T element)
//    {
//        return this.remove(element);
//    }

//    //-----------------------------------------------------------------------------
//    // Removes the element at the specified position in the list.
//    //-----------------------------------------------------------------------------
//    public T removeElement(int index)
//    {
//        return this.remove(index);
//    }

    //-----------------------------------------------------------------------------
    // Returns the element at the specified position in this list.
    //-----------------------------------------------------------------------------
    public T get(int index)
    {
//        if ( this.isEmpty() )
//        {
//            return null;
//        }
//        else
//        {
//            return (Object) _list.get(index);
//        }
        T element = super.get(index); // Throws "IndexOutOfBoundsException" if the specified index is is out of range (index < 0 || index >= size()).
        _iCurrentIndex = index ;
        return element ;
    }

    //-----------------------------------------------------------------------------
    // Returns the first element in the list.
    //-----------------------------------------------------------------------------
    public T getFirst()
    {
//        if (this.isEmpty())
//        {
//            _iCurrentIndex = 0;
//            return null;
//        }
//        else
//        {
//            _iCurrentIndex = 0;
//            return this.get(_iCurrentIndex);
//        }
        T element = super.getFirst(); // Throws "NoSuchElementException" if this list is empty.
        _iCurrentIndex = 0 ;
        return element ;
    }

    //-----------------------------------------------------------------------------
    public T getLast()
    {
        T element = super.getLast(); // Throws "NoSuchElementException" if this list is empty.
        _iCurrentIndex = this.size()-1 ;
        return element ;
    }

    //-----------------------------------------------------------------------------
    // Returns the next element in the list.
    //-----------------------------------------------------------------------------
    /**
     * Returns the next element after the current position, or null if none
     * @return the next element or null
     */
    public T getNext()
    {
        if (super.isEmpty())
        {
            //_iCurrentIndex = 0;
            return null;
        }
        else
        {
            if (_iCurrentIndex + 1 < this.size() )
            {
                _iCurrentIndex++;
                return super.get(_iCurrentIndex);
            }
            else
            {
                return null;
            }
        }
    }

    public int getCurrentPosition()
    {
    	return _iCurrentIndex;
    }
    
    public void resetPosition()
    {
    	_iCurrentIndex = 0 ;
    }
    
    //-----------------------------------------------------------------------------
    // PUBLIC METHOD(S)
    //-----------------------------------------------------------------------------
    /**
     * Returns the type (class) of the elements stored in the list
     * 
     * @return
     */
    public Class<T> getElementType()
    {
        return _elementClass;
    }

//    //-----------------------------------------------------------------------------
//    /**
//     * Returns the encapsulated List object
//     * 
//     * @return
//     */
//    public List<T> getList()
//    {
//        return _list;
//    }

//    //-----------------------------------------------------------------------------
//    /**
//     * Returns an iterator over the elements in this list in proper sequence
//     * 
//     * @return the iterator
//     */
//    public Iterator<T> iterator()
//    {
//        return _list.iterator();
//    }

//    //-----------------------------------------------------------------------------
//    /**
//     * Returns a list iterator of the elements in this list (in proper sequence)
//     * 
//     * @return the list iterator
//     */
//    public ListIterator<T> listIterator()
//    {
//        return _list.listIterator();
//    }

//    //-----------------------------------------------------------------------------
//    /**
//     * Returns the number of elements in this list.
//     * 
//     * @return
//     */
//    public int size()
//    {
//        return _list.size();
//    }

//    //-----------------------------------------------------------------------------
//    /**
//     * Returns 'true' if this list contains no elements
//     * 
//     * @return
//     */
//    public boolean isEmpty()
//    {
//        return _list.isEmpty();
//    }

//    //-----------------------------------------------------------------------------
//    /**
//     * Removes all of the elements from this list
//     */
//    public void clear()
//    {
//        _list.clear();
//    }
    
    //-----------------------------------------------------------------------------
    /**
     * Select the given index   
     * @param iNewSelectedIndex
     * @return the previous selected index
     */
    public int select( int iNewSelectedIndex )
    {
        int iRet = _iSelectedIndex ;
        if ( super.isEmpty() )
        {
            throw new TelosysRuntimeException("Cannot select : the list is empty");
        }
        else
        {
            if ( iNewSelectedIndex >= 0 && iNewSelectedIndex < this.size() )
            {
                _iSelectedIndex = iNewSelectedIndex ;                
            }
            else
            {
                throw new TelosysRuntimeException("select("+iNewSelectedIndex+") : out of bounds (list size =" + this.size() + ")" );                
            }
        }
        return iRet ;
    }
    //-----------------------------------------------------------------------------
    /**
     * Set the list in a state where there's no selected element (no selected index)  
     * @return the previous selected index
     */
    public int unselect()
    {
        int iRet = _iSelectedIndex ;
        _iSelectedIndex = UNSELECTED ;
        return iRet ;
    }
    //-----------------------------------------------------------------------------
    /**
     * Returns the current selected index (or -1 if no selection)
     * @return
     */
    public int getSelectedIndex()
    {
        return _iSelectedIndex ;
    }
    //-----------------------------------------------------------------------------
    /**
     * Returns true if the given index is the current selected index
     * @param iIndex
     * @return
     */
    public boolean isSelected( int iIndex )
    {
        return (  iIndex == _iSelectedIndex ? true : false ) ;
    }
    //-----------------------------------------------------------------------------
    /**
     * Returns the current selected element (or null if no element selected) 
     * @return
     */
    public T getSelectedElement()
    {
        if ( _iSelectedIndex >= 0 && _iSelectedIndex < this.size() )
        {
            return this.get( _iSelectedIndex ) ;
        }
        return null ;
    }
    //-----------------------------------------------------------------------------
    /**
     * Replaces the current selected element if any
     * @param newElement
     * @return true if done (if there is a valid current selected index), else false 
     */
    public boolean replaceSelectedElement(T newElement)
    {
        if ( _iSelectedIndex >= 0 && _iSelectedIndex < this.size() )
        {
        	super.set(_iSelectedIndex, newElement);
            return true ;
        }
        return false ;
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the field name used as current sort criteria
     * 
     * @return : field name, or "" if the list is not sorted
     */
    public String getSortField()
    {
        if (_bSorted && (_sSortedByField != null))
        {
            return _sSortedByField;
        }
        return "";
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns true if the list is currently sorted by the given field
     * 
     * @param sFieldName
     * @return true/false
     */
    public boolean isSortedBy(String sFieldName)
    {
        if (sFieldName == null)
        {
            return false;
        }
        if (_bSorted && (_sSortedByField != null))
        {
            if (_sSortedByField.equalsIgnoreCase(sFieldName.trim()))
            {
                return true;
            }
        }
        return false;
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns true if the list is currently sorted in ascending order
     * @return
     */
    public boolean isSortedInAscendingOrder()
    {
        if (_bSorted && (_bSortOrder == FieldComparer.ASC))
        {
            return true;
        }
        return false;
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns true if the list is currently sorted in descending order
     * @return
     */
    public boolean isSortedInDescendingOrder()
    {
        if (_bSorted && (_bSortOrder == FieldComparer.DESC))
        {
            return true;
        }
        return false;
    }

    //-----------------------------------------------------------------------------
    /**
     * Sort the list using the given field as criteria (case sensitive) in ascending order
     * 
     * @param sFieldName :
     *            the name of the field to compare
     */
    public void sort(String sFieldName)
    {
        sortList(sFieldName, FieldComparer.ASC, FieldComparer.DO_NOT_IGNORE_CASE);
    }

    //-----------------------------------------------------------------------------
    /**
     * Sort the list using the given field as criteria (case sensitive) and the specified order ( ascending or descending )
     * 
     * @param sFieldName :
     *            the name of the field to compare
     * @param sOrder :
     *            'ASC' = ascending order , 'DESC' = descending order
     */
    public void sort(String sFieldName, String sOrder)
    {
        boolean bOrder = getSortOrderFlag(sOrder);
        sortList(sFieldName, bOrder, FieldComparer.DO_NOT_IGNORE_CASE);
    }

    //-----------------------------------------------------------------------------
    /**
     * Sort the list using the given field as criteria ( ignoring case ) in ascending order
     * 
     * @param sFieldName :
     *            the name of the field to compare
     */
    public void sortIgnoreCase(String sFieldName)
    {
        sortList(sFieldName, FieldComparer.ASC, FieldComparer.IGNORE_CASE);
    }

    //-----------------------------------------------------------------------------
    /**
     * Sort the list using the given field as criteria ( ignoring case ) and the specified order ( ascending or descending )
     * 
     * @param sFieldName :
     *            the name of the field to compare
     * @param sOrder :
     *            'ASC' = ascending order , 'DESC' = descending order
     */
    public void sortIgnoreCase(String sFieldName, String sOrder)
    {
        boolean bOrder = getSortOrderFlag(sOrder);
        sortList(sFieldName, bOrder, FieldComparer.IGNORE_CASE);
    }
    
    //-----------------------------------------------------------------------------
    public String toString()
    {
        StringBuffer sb = new StringBuffer(512);
        sb.append(this.getClass().getName() + " : \n");
        if ( super.isEmpty() ) {
            sb.append(" (empty) \n");
        }
        else {
        	for ( T element : this ) {
        		if ( element != null ) {
                    sb.append(" . " + element.toString() + "\n");
        		}
        		else {
                    sb.append(" . (null) \n");
        		}
        	}
        }
//        Object obj = getFirstElement();
//        while (obj != null)
//        {
//            sb.append(" . " + obj.toString() + "\n");
//            obj = getNextElement();
//        }
        return sb.toString();
    }
}